﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace Dasaar.Interaction.TellStick
{
    public sealed class TelldusCore
    {
        #region "Infrastructure Imports"

        [DllImport("TelldusCore.dll")]
        private static extern int tdGetNumberOfDevices();
        public static int GetNumberOfDevices()
        {
            return tdGetNumberOfDevices();
        }

        [DllImport("TelldusCore.dll")]
        private static extern int tdGetDeviceId(int value);
        public static int GetDeviceID(int deviceIdx)
        {
            return tdGetDeviceId(deviceIdx);
        }

        [DllImport("TelldusCore.dll")]
        private static unsafe extern int tdAddDevice();
        public static int AddDevice()
        {
            return tdAddDevice();
        }

        [DllImport("TelldusCore.dll")]
        private static unsafe extern bool tdRemoveDevice(int deviceId);
        public static bool RemoveDevice(int deviceId)
        {
            return tdRemoveDevice(deviceId);
        }

        [DllImport("TelldusCore.dll")]
        private static extern void tdClose();
        public static void CloseDLL()
        {
            tdClose();
        }

        [DllImport("TelldusCore.dll")]
        private static extern void tdInit();
        public static void InitDLL()
        {
            tdInit();
        }

        [DllImport("TelldusCore.dll")]
        private static unsafe extern char* tdGetErrorString(int errorNo);
        private static unsafe string tdGetErrorStringEx(int errorNo)
        {
            return GetString(tdGetErrorString(errorNo));
        }
        public static string GetErrorMessage(int errorCode)
        {
            return tdGetErrorStringEx(errorCode);
        }
        
        #endregion

        #region "Device command Imports"
        [DllImport("TelldusCore.dll")]
        private static unsafe extern char* tdGetName(int deviceId);
        private static unsafe string tdGetNameEx(int deviceId)
        {
            return GetString(tdGetName(deviceId));
        }
        public static string GetName(int deviceId)
        {
            return tdGetNameEx(deviceId);
        }

        [DllImport("TelldusCore.dll")]
        private static unsafe extern char* tdGetProtocol(int deviceId);
        private static unsafe string tdGetProtocolEx(int deviceId)
        {
            return GetString(tdGetProtocol(deviceId));
        }
        public static DeviceProtocol GetProtocol(int deviceId)
        {
            string protocolName = tdGetProtocolEx(deviceId);
            return (DeviceProtocol)Enum.Parse(typeof(DeviceProtocol), protocolName,true);
        }

        [DllImport("TelldusCore.dll")]
        private static unsafe extern char* tdGetModel(int deviceId);
        private static unsafe string tdGetModelEx(int deviceId)
        {
            return GetString(tdGetModel(deviceId));
        }
        public static string GetModel(int deviceId)
        {
            return tdGetModelEx(deviceId);
        }

        [DllImport("TelldusCore.dll")]
        private static unsafe extern bool tdSetName(int deviceId, char* name);
        private static unsafe bool tdSetNameEx(int deviceId, string name)
        {
            return tdSetName(deviceId, StringToChar(name));
        }
        public static bool SetName(int deviceId, string name)
        {
            return tdSetNameEx(deviceId, name);
        }

        [DllImport("TelldusCore.dll")]
        private static unsafe extern bool tdSetProtocol(int deviceId, char* protocol);
        private static unsafe bool tdSetProtocolEx(int deviceId, string protocol)
        {
            return tdSetProtocol(deviceId, StringToChar(protocol));
        }
        public static bool SetProtocol(int deviceId, DeviceProtocol protocol)
        {
            return tdSetProtocolEx(deviceId, protocol.ToString().ToLower());
        }

        [DllImport("TelldusCore.dll")]
        private static unsafe extern bool tdSetModel(int deviceId, char* model);
        private static unsafe bool tdSetModelEx(int deviceId, string model)
        {
            return tdSetModel(deviceId, StringToChar(model));
        }
        public static bool SetModel(int deviceId, string model)
        {
            return tdSetModelEx(deviceId, model);
        }

        [DllImport("TelldusCore.dll")]
        private static unsafe extern int tdMethods(int deviceId, int methodsSupported);
        public static DeviceMethods GetDeviceMethods(int deviceId, DeviceMethods methods)
        {
            DeviceMethods result = new DeviceMethods();
            int flags = tdMethods(deviceId, (int)methods);

            Array enumArray = Enum.GetValues(typeof(DeviceMethods));
            DeviceMethods[] enumValues = (DeviceMethods[])enumArray;
            int[] intValues = (int[])enumArray;

            for (int i = 0; i < intValues.Length; i++)
            {
                if (intValues[i] > 0)
                {
                    int iTest = intValues[i] & flags;
                    if (iTest == intValues[i])
                    {
                        result |= enumValues[i];
                    }
                }

            }

            return result;
        }

        [DllImport("TelldusCore.dll")]
        private static extern int tdLearn(int deviceId);
        public static void Learn(int deviceId)
        {
            int errorCode = tdLearn(deviceId);
            if (errorCode != 0)
                throw MakeMethodException(errorCode, DeviceMethods.Learn);

        }

        [DllImport("TelldusCore.dll")]
        private static extern int tdTurnOn(int deviceId);
        public static void On(int deviceId)
        {
            int errorCode = tdTurnOn(deviceId);
            if (errorCode != 0)
                throw MakeMethodException(errorCode, DeviceMethods.On);

        }

        [DllImport("TelldusCore.dll")]
        private static extern int tdTurnOff(int deviceId);
        public static void Off(int deviceId)
        {
            int errorCode = tdTurnOff(deviceId);
            if (errorCode != 0)
                throw MakeMethodException(errorCode, DeviceMethods.Off);

        }

        [DllImport("TelldusCore.dll")]
        private static extern int tdBell(int deviceId);
        public static void Bell(int deviceId)
        {
            int errorCode = tdBell(deviceId);
            if (errorCode != 0)
                throw MakeMethodException(errorCode, DeviceMethods.Bell);

        }

        public static void Toggle(int deviceId)
        {
            throw new NotImplementedException("Toggle command is not implemented in native interface");
        }

        [DllImport("TelldusCore.dll")]
        private static extern int tdDim(int deviceId, char level);
        private static int tdDimEx(int deviceId, int level)
        {
            return tdDim(deviceId, Convert.ToChar(level));
        }
        public static void Dim(int deviceId, int level)
        {
            if (level < 0 || level > 255)
                throw new ArgumentOutOfRangeException("The dimmer level must be a value between 0 and 255");

            int errorCode = tdDimEx(deviceId, level);
            if (errorCode != 0)
                throw MakeMethodException(errorCode, DeviceMethods.Dim);

        }

        [DllImport("TelldusCore.dll")]
        private static extern int tdExecute(int deviceId);
        public static void Execute(int deviceId)
        {
            int errorCode = tdExecute(deviceId);
            if (errorCode != 0)
                throw MakeMethodException(errorCode, DeviceMethods.Execute);

        }

        [DllImport("TelldusCore.dll")]
        private static extern int tdUp(int deviceId);
        public static void Up(int deviceId)
        {
            int errorCode = tdUp(deviceId);
            if (errorCode != 0)
                throw MakeMethodException(errorCode, DeviceMethods.Up);

        }

        [DllImport("TelldusCore.dll")]
        private static extern int tdDown(int deviceId);
        public static void Down(int deviceId)
        {
            int errorCode = tdDown(deviceId);
            if (errorCode != 0)
                throw MakeMethodException(errorCode, DeviceMethods.Down);

        }

        [DllImport("TelldusCore.dll")]
        private static extern int tdStop(int deviceId);
        public static void Stop(int deviceId)
        {
            int errorCode = tdStop(deviceId);
            if (errorCode != 0)
                throw MakeMethodException(errorCode, DeviceMethods.Stop);

        }

        [DllImport("TelldusCore.dll")]
        private static unsafe extern char* tdGetDeviceParameter(int deviceId, char* name, char* defaultValue);
        private static unsafe string tdGetDeviceParameterEx(int deviceId, string name, string devaultValue)
        {
            return GetString(tdGetDeviceParameter(deviceId, StringToChar(name), StringToChar(devaultValue)));
        }
        private static string GetDeviceParameter(int deviceId, string name, string defaultValue)
        {
            return tdGetDeviceParameterEx(deviceId, name, defaultValue);
        }

        [DllImport("TelldusCore.dll")]
        private static unsafe extern bool tdSetDeviceParameter(int deviceId, char* name, char* value);
        private static unsafe bool tdSetDeviceParameterEx(int deviceId, string name, string value)
        {
            return tdSetDeviceParameter(deviceId, StringToChar(name), StringToChar(value));
        }
        public static void SetDeviceParameter(int deviceId, string name, string value)
        {
            if (!tdSetDeviceParameterEx(deviceId, name, value))
                throw new TellStickInvokationException("Could not set parameter, native library returned false");
        }

        [DllImport("TelldusCore.dll")]
        private static extern int tdGetDeviceType(int deviceId);
        public static DeviceType GetDeviceType(int deviceId)
        {
            return (DeviceType)tdGetDeviceType(deviceId);
        }
        #endregion

        #region "PrivateImports"
        //
        // Used only for internal usage in the TeldusCore
        // Theese are typically used to abstract something in the above
        // 
        [DllImport("TelldusCore.dll")]
        private static unsafe extern void tdReleaseString(char* value);
        #endregion

        #region "Private methods"

        private static Exception MakeMethodException(int errorCode, DeviceMethods method)
        {
            return new TellStickMethodException(string.Format("A error occured while performing method {0}: {1}", method, GetErrorMessage(errorCode)));
        }

        private static Exception MakeInvokationException(int errorCode)
        {
            return new TellStickInvokationException(string.Format("A error occured while performing the invokation: {0}", GetErrorMessage(errorCode)));
        }


        private static unsafe string GetString(char* input)
        {
            return GetString(input, true);
        }

        private static unsafe string GetString(char* input, bool release)
        {
            string returnString = System.Text.Encoding.UTF8.GetString(System.Text.Encoding.Unicode.GetBytes(new string(input)));

            if (returnString.Contains("\0"))
                returnString = returnString.Substring(0, returnString.IndexOf('\0'));

            if (release)
                tdReleaseString(input);

            GC.Collect();
            return returnString;
        }

        private static unsafe char* StringToChar(string input)
        {
            input = String.Concat(input, char.MinValue);	//add null terminator
            byte[] inputByte = System.Text.Encoding.UTF8.GetBytes(input);
            int size = Marshal.SystemDefaultCharSize * inputByte.Length;
            IntPtr inputPointer = Marshal.AllocHGlobal(size);

            Marshal.Copy(inputByte, 0, inputPointer, inputByte.Length);
            return (char*)inputPointer;
        }

        #endregion

    }
}
